use crate::{
    ast::{types, Expression, Type},
    error::{CompilerError, Result},
    llvm_backend::LLVMBackend,
};
use inkwell::{types::BasicType, values::BasicValueEnum, AddressSpace};

impl<'ctx> LLVMBackend<'ctx> {
    /// Generate slice.remove(index) method
    pub(in crate::llvm_backend) fn generate_slice_remove_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 1 {
            return Err(CompilerError::internal(
                "remove() requires exactly one argument (index)",
            ));
        }

        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal(
                "remove() requires a slice variable",
            ));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Generate the index argument
        let index_value = self.generate_expression(&arguments[0])?;
        let mut index_int = index_value.into_int_value();

        // Convert index to i64 if necessary
        if index_int.get_type() != self.context.i64_type() {
            index_int = self.builder.build_int_z_extend_or_bit_cast(
                index_int,
                self.context.i64_type(),
                "index_i64",
            )?;
        }

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract fields from the slice struct
        let ptr = self
            .builder
            .build_extract_value(slice_struct, 0, "ptr")?
            .into_pointer_value();
        let len = self
            .builder
            .build_extract_value(slice_struct, 1, "len")?
            .into_int_value();
        let cap = self
            .builder
            .build_extract_value(slice_struct, 2, "cap")?
            .into_int_value();

        // Create basic blocks for control flow
        let parent_fn = self
            .builder
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CompilerError::internal("No parent function for remove"))?;

        let bounds_check_block = self.context.append_basic_block(parent_fn, "bounds_check");
        let remove_block = self.context.append_basic_block(parent_fn, "remove_element");
        let merge_block = self.context.append_basic_block(parent_fn, "remove_merge");

        // Check bounds: index >= 0 && index < len
        let zero = self.context.i64_type().const_int(0, false);
        let index_ge_zero = self.builder.build_int_compare(
            inkwell::IntPredicate::SGE,
            index_int,
            zero,
            "index_ge_zero",
        )?;

        let index_lt_len = self.builder.build_int_compare(
            inkwell::IntPredicate::SLT,
            index_int,
            len,
            "index_lt_len",
        )?;

        let in_bounds = self
            .builder
            .build_and(index_ge_zero, index_lt_len, "in_bounds")?;

        // Branch based on bounds check
        let _ = self
            .builder
            .build_conditional_branch(in_bounds, remove_block, bounds_check_block);

        // Bounds check failed: return zero value (or could panic in future)
        self.builder.position_at_end(bounds_check_block);
        let zero_element = self.generate_zero_value(&element_type)?;
        let _ = self.builder.build_unconditional_branch(merge_block);

        // Remove case: get element at index and shift remaining elements
        self.builder.position_at_end(remove_block);

        // Get pointer to element at index
        let element_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, ptr, &[index_int], "element_ptr")?
        };

        // Load the element to be removed
        let removed_element =
            self.builder
                .build_load(element_llvm_type, element_ptr, "removed_element")?;

        // Shift elements: move elements from index+1 to len-1 one position left
        // This is done by copying each element to the previous position
        let one = self.context.i64_type().const_int(1, false);
        let loop_start = self.builder.build_int_add(index_int, one, "loop_start")?;

        // Create loop blocks
        let loop_block = self.context.append_basic_block(parent_fn, "shift_loop");
        let loop_body = self.context.append_basic_block(parent_fn, "shift_body");
        let loop_end = self.context.append_basic_block(parent_fn, "shift_end");

        // Initialize loop variable
        let _ = self.builder.build_unconditional_branch(loop_block);
        self.builder.position_at_end(loop_block);

        // Create PHI node for loop variable
        let i_phi = self.builder.build_phi(self.context.i64_type(), "i")?;
        i_phi.add_incoming(&[(&loop_start, remove_block)]);

        let i = i_phi.as_basic_value().into_int_value();

        // Check loop condition: i < len
        let loop_condition =
            self.builder
                .build_int_compare(inkwell::IntPredicate::SLT, i, len, "loop_condition")?;

        let _ = self
            .builder
            .build_conditional_branch(loop_condition, loop_body, loop_end);

        // Loop body: copy element from position i to position i-1
        self.builder.position_at_end(loop_body);

        // Get source element (at position i)
        let src_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, ptr, &[i], "src_ptr")?
        };
        let src_element = self
            .builder
            .build_load(element_llvm_type, src_ptr, "src_element")?;

        // Get destination element (at position i-1)
        let i_minus_one = self.builder.build_int_sub(i, one, "i_minus_one")?;
        let dst_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, ptr, &[i_minus_one], "dst_ptr")?
        };

        // Copy element
        self.builder.build_store(dst_ptr, src_element)?;

        // Increment loop variable
        let i_next = self.builder.build_int_add(i, one, "i_next")?;
        i_phi.add_incoming(&[(&i_next, loop_body)]);

        let _ = self.builder.build_unconditional_branch(loop_block);

        // End of shift loop
        self.builder.position_at_end(loop_end);

        // Create new slice with decremented length
        let new_len = self.builder.build_int_sub(len, one, "new_len")?;

        // Create new slice struct
        let new_slice_alloca = self.builder.build_alloca(slice_struct_type, "new_slice")?;

        // Store ptr field (same pointer)
        let ptr_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(0, false),
                ],
                "ptr_field",
            )?
        };
        self.builder.build_store(ptr_field_ptr, ptr)?;

        // Store new len field
        let len_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(1, false),
                ],
                "len_field",
            )?
        };
        self.builder.build_store(len_field_ptr, new_len)?;

        // Store cap field (same capacity)
        let cap_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(2, false),
                ],
                "cap_field",
            )?
        };
        self.builder.build_store(cap_field_ptr, cap)?;

        // Load the new slice
        let new_slice =
            self.builder
                .build_load(slice_struct_type, new_slice_alloca, "new_slice")?;

        // Store the new slice back to the variable
        self.builder.build_store(slice_ptr, new_slice)?;

        let _ = self.builder.build_unconditional_branch(merge_block);

        // Merge block: create PHI node for the result
        self.builder.position_at_end(merge_block);

        // Create PHI node for the removed element
        let phi = self
            .builder
            .build_phi(element_llvm_type, "removed_element_phi")?;
        phi.add_incoming(&[
            (&zero_element, bounds_check_block),
            (&removed_element, loop_end),
        ]);

        Ok(phi.as_basic_value())
    }

    /// Generate slice.insert(index, value) method
    pub(in crate::llvm_backend) fn generate_slice_insert_method(
        &mut self,
        var_name: &str,
        arguments: &[Expression],
    ) -> Result<BasicValueEnum<'ctx>> {
        // Validate arguments
        if arguments.len() != 2 {
            return Err(CompilerError::internal(
                "insert() requires exactly two arguments (index, value)",
            ));
        }

        // Get the slice variable pointer from variable_map
        let slice_ptr = self
            .variable_map
            .get(var_name)
            .ok_or_else(|| {
                CompilerError::internal(format!("Undefined slice variable: {}", var_name))
            })?
            .into_pointer_value();

        // Get the slice type
        let vector_type = self.variable_type_map.get(var_name).ok_or_else(|| {
            CompilerError::internal(format!("Undefined slice type for variable: {}", var_name))
        })?;

        let element_type = if let Type::Vec(slice_t) = vector_type {
            *slice_t.element_type.clone()
        } else {
            return Err(CompilerError::internal(
                "insert() requires a slice variable",
            ));
        };

        // Get the element LLVM type
        let element_llvm_type = self.nrc_type_to_llvm_type(&Some(element_type.clone()))?;

        // Generate the index and value arguments
        let index_value = self.generate_expression(&arguments[0])?;
        let mut index_int = index_value.into_int_value();

        // Convert index to i64 if necessary
        if index_int.get_type() != self.context.i64_type() {
            index_int = self.builder.build_int_z_extend_or_bit_cast(
                index_int,
                self.context.i64_type(),
                "index_i64",
            )?;
        }

        let value_to_insert = self.generate_expression(&arguments[1])?;

        // Create slice struct type
        let ptr_type = element_llvm_type.ptr_type(AddressSpace::default());
        let slice_struct_type = self.context.struct_type(
            &[
                ptr_type.into(),
                self.context.i64_type().into(),
                self.context.i64_type().into(),
            ],
            false,
        );

        // Load current slice
        let current_slice =
            self.builder
                .build_load(slice_struct_type, slice_ptr, "current_slice")?;

        if !current_slice.is_struct_value() {
            return Err(CompilerError::internal("Slice value is not a struct"));
        }

        let slice_struct = current_slice.into_struct_value();

        // Extract fields from the slice struct
        let ptr = self
            .builder
            .build_extract_value(slice_struct, 0, "ptr")?
            .into_pointer_value();
        let len = self
            .builder
            .build_extract_value(slice_struct, 1, "len")?
            .into_int_value();
        let cap = self
            .builder
            .build_extract_value(slice_struct, 2, "cap")?
            .into_int_value();

        // Create basic blocks for control flow
        let parent_fn = self
            .builder
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CompilerError::internal("No parent function for insert"))?;

        let bounds_check_block = self
            .context
            .append_basic_block(parent_fn, "insert_bounds_check");
        let insert_block = self.context.append_basic_block(parent_fn, "insert_element");
        let merge_block = self.context.append_basic_block(parent_fn, "insert_merge");

        // Check bounds: index >= 0 && index <= len (can insert at end)
        let zero = self.context.i64_type().const_int(0, false);
        let index_ge_zero = self.builder.build_int_compare(
            inkwell::IntPredicate::SGE,
            index_int,
            zero,
            "index_ge_zero",
        )?;

        let index_le_len = self.builder.build_int_compare(
            inkwell::IntPredicate::SLE,
            index_int,
            len,
            "index_le_len",
        )?;

        let in_bounds = self
            .builder
            .build_and(index_ge_zero, index_le_len, "in_bounds")?;

        // Branch based on bounds check
        let _ = self
            .builder
            .build_conditional_branch(in_bounds, insert_block, bounds_check_block);

        // Bounds check failed: return zero value
        self.builder.position_at_end(bounds_check_block);
        let zero_element = self.generate_zero_value(&element_type)?;
        let _ = self.builder.build_unconditional_branch(merge_block);

        // Insert case: check if capacity is sufficient and handle reallocation if needed
        self.builder.position_at_end(insert_block);

        let one = self.context.i64_type().const_int(1, false);
        let new_len = self.builder.build_int_add(len, one, "new_len")?;

        // Check if we need to reallocate (new_len > cap)
        let need_realloc = self.builder.build_int_compare(
            inkwell::IntPredicate::UGT,
            new_len,
            cap,
            "need_realloc_insert",
        )?;

        // Create basic blocks for reallocation
        let realloc_block = self
            .context
            .append_basic_block(parent_fn, "insert_realloc_block");
        let no_realloc_block = self
            .context
            .append_basic_block(parent_fn, "insert_no_realloc_block");
        let after_realloc_block = self
            .context
            .append_basic_block(parent_fn, "insert_after_realloc");

        // Branch based on reallocation need
        let _ =
            self.builder
                .build_conditional_branch(need_realloc, realloc_block, no_realloc_block);

        // Realloc block: allocate new memory
        self.builder.position_at_end(realloc_block);

        // Calculate new capacity (double old capacity or at least new_len)
        let new_cap_doubled = self.builder.build_int_mul(
            cap,
            self.context.i64_type().const_int(2, false),
            "new_cap_doubled",
        )?;
        let new_cap = self
            .builder
            .build_select(
                self.builder.build_int_compare(
                    inkwell::IntPredicate::ULT,
                    new_cap_doubled,
                    new_len,
                    "cap_check_insert",
                )?,
                new_len,
                new_cap_doubled,
                "new_cap_insert",
            )?
            .into_int_value();

        // Get element size. Default to pointer size for non-basic types,
        // except Rc/Weak trait handles which are {ptr, ptr}.
        let element_size = match &element_type {
            Type::Basic(basic_type) => match basic_type {
                types::BasicType::Int8 | types::BasicType::Uint8 | types::BasicType::Char => 1,
                types::BasicType::Int16 | types::BasicType::Uint16 => 2,
                types::BasicType::Int32 | types::BasicType::Uint32 | types::BasicType::Float32 => 4,
                types::BasicType::Int
                | types::BasicType::Int64
                | types::BasicType::Uint
                | types::BasicType::Uint64
                | types::BasicType::Float64 => 8,
                types::BasicType::Bool => 1,
                _ => 8,
            },
            Type::Rc(rc_type) => {
                if self.trait_name_from_type(&rc_type.inner_type).is_some() {
                    16
                } else {
                    8
                }
            }
            Type::Weak(weak_type) => {
                if self.trait_name_from_type(&weak_type.inner_type).is_some() {
                    16
                } else {
                    8
                }
            }
            _ => 8,
        };
        let element_size_const = self.context.i64_type().const_int(element_size, false);

        // Allocate new memory
        let new_size_bytes =
            self.builder
                .build_int_mul(new_cap, element_size_const, "new_size_bytes_insert")?;
        let malloc_func = self
            .function_map
            .get("__malloc")
            .ok_or_else(|| CompilerError::internal("__malloc function not found"))?;
        let malloc_call =
            self.builder
                .build_call(*malloc_func, &[new_size_bytes.into()], "malloc_insert")?;
        let new_ptr_i8 = malloc_call
            .try_as_basic_value()
            .left()
            .ok_or_else(|| CompilerError::internal("malloc call did not return a value"))?
            .into_pointer_value();
        let new_ptr = self
            .builder
            .build_bitcast(
                new_ptr_i8,
                element_llvm_type.ptr_type(AddressSpace::default()),
                "new_ptr_insert",
            )?
            .into_pointer_value();

        // Copy old elements using memcpy
        let copy_size = self
            .builder
            .build_int_mul(len, element_size_const, "copy_size_insert")?;
        let memcpy_intrinsic = self
            .module
            .get_function("llvm.memcpy.p0.p0.i64")
            .unwrap_or_else(|| {
                let i8_ptr_type = self.context.i8_type().ptr_type(AddressSpace::default());
                let memcpy_type = self.context.void_type().fn_type(
                    &[
                        i8_ptr_type.into(),
                        i8_ptr_type.into(),
                        self.context.i64_type().into(),
                        self.context.bool_type().into(),
                    ],
                    false,
                );
                self.module
                    .add_function("llvm.memcpy.p0.p0.i64", memcpy_type, None)
            });

        let dest_i8 = self
            .builder
            .build_bitcast(
                new_ptr,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "dest_i8_insert",
            )?
            .into_pointer_value();
        let src_i8 = self
            .builder
            .build_bitcast(
                ptr,
                self.context.i8_type().ptr_type(AddressSpace::default()),
                "src_i8_insert",
            )?
            .into_pointer_value();

        self.builder.build_call(
            memcpy_intrinsic,
            &[
                dest_i8.into(),
                src_i8.into(),
                copy_size.into(),
                self.context.bool_type().const_int(0, false).into(),
            ],
            "memcpy_insert",
        )?;

        // Free old memory
        let free_func = self
            .function_map
            .get("free")
            .ok_or_else(|| CompilerError::internal("free function not found"))?;
        self.builder
            .build_call(*free_func, &[src_i8.into()], "free_old_memory_insert")?;

        let _ = self.builder.build_unconditional_branch(after_realloc_block);

        // No realloc block: use existing pointer
        self.builder.position_at_end(no_realloc_block);
        let _ = self.builder.build_unconditional_branch(after_realloc_block);

        // After realloc block: phi nodes for ptr and cap
        self.builder.position_at_end(after_realloc_block);
        let final_ptr_phi = self.builder.build_phi(
            element_llvm_type.ptr_type(AddressSpace::default()),
            "final_ptr_insert",
        )?;
        final_ptr_phi.add_incoming(&[(&new_ptr, realloc_block), (&ptr, no_realloc_block)]);
        let final_ptr = final_ptr_phi.as_basic_value().into_pointer_value();

        let final_cap_phi = self
            .builder
            .build_phi(self.context.i64_type(), "final_cap_insert")?;
        final_cap_phi.add_incoming(&[(&new_cap, realloc_block), (&cap, no_realloc_block)]);
        let final_cap = final_cap_phi.as_basic_value().into_int_value();

        // Insert the new element at the specified index
        // For simplicity, only allow insertion at the end (index == len)
        // TODO: Implement proper insertion with element shifting
        let insert_ptr = unsafe {
            self.builder
                .build_gep(element_llvm_type, final_ptr, &[index_int], "insert_ptr")?
        };
        self.builder.build_store(insert_ptr, value_to_insert)?;

        // Create new slice with incremented length
        let new_slice_alloca = self.builder.build_alloca(slice_struct_type, "new_slice")?;

        // Store ptr field
        let ptr_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(0, false),
                ],
                "ptr_field",
            )?
        };
        self.builder.build_store(ptr_field_ptr, final_ptr)?;

        // Store new len field
        let len_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(1, false),
                ],
                "len_field",
            )?
        };
        self.builder.build_store(len_field_ptr, new_len)?;

        // Store cap field
        let cap_field_ptr = unsafe {
            self.builder.build_gep(
                slice_struct_type,
                new_slice_alloca,
                &[
                    self.context.i32_type().const_int(0, false),
                    self.context.i32_type().const_int(2, false),
                ],
                "cap_field",
            )?
        };
        self.builder.build_store(cap_field_ptr, final_cap)?;

        // Load the new slice
        let new_slice =
            self.builder
                .build_load(slice_struct_type, new_slice_alloca, "new_slice")?;

        // Store the new slice back to the variable
        self.builder.build_store(slice_ptr, new_slice)?;

        let _ = self.builder.build_unconditional_branch(merge_block);

        // Merge block: create PHI node for the result
        self.builder.position_at_end(merge_block);

        // Create PHI node for the result (inserted value or zero if failed)
        let phi = self
            .builder
            .build_phi(element_llvm_type, "insert_result_phi")?;
        phi.add_incoming(&[
            (&zero_element, bounds_check_block),
            (&value_to_insert, after_realloc_block),
        ]);

        Ok(phi.as_basic_value())
    }
}
